package Servlet_Agent;


import com.sun.net.httpserver.HttpServer;
import javassist.*;

import javax.sound.midi.Soundbank;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

public class HttpCollect implements ClassFileTransformer {
    private static final String Target_class = "javax.servlet.http.HttpServlet" ;
    private static final String Target_method = "service";
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if(!Target_class.replace(".","/").equals(className)) {
           // System.out.println(className);
            return null;
        }
        try {
            System.out.println("HTTP-响应监控");
            return buildClass(loader);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new byte[0];
    }
    public byte[] buildClass(ClassLoader classLoader) throws Exception {

        ClassPool pool = new ClassPool();
        pool.insertClassPath(new LoaderClassPath(classLoader));
        CtClass ctClass = pool.get(Target_class);
        CtMethod oldMethod = ctClass.getDeclaredMethod(Target_method);
        CtMethod newMethod = CtNewMethod.copy(oldMethod, ctClass, null);
        oldMethod.setName(oldMethod.getName() + "$agent");
        String beginSrc = "Object stat = Servlet_Agent.HttpCollect.begin($args);";
        String errorSrc = "Servlet_Agent.HttpCollect.error(e, stat);";
        String endSrc = "Servlet_Agent.HttpCollect.end(stat);";
        newMethod.setBody(String.format(voidSource, beginSrc, Target_method, errorSrc, endSrc));
        ctClass.addMethod(newMethod);
        return ctClass.toBytecode();
    }

    // url,client IP
    public static HttpInfo begin(Object args[]) {
        HttpInfo info = new HttpInfo();
        info.beginTime=System.currentTimeMillis();
        HttpServletRequestAdapter adapter = new HttpServletRequestAdapter(args[0]);
        info.url=adapter.getRequestURI();
        info.clientIp=adapter.getClientIp();
        return info;
    }

    public static void end(Object obj) {
        HttpInfo stat = (HttpInfo) obj;
        stat.useTime=System.currentTimeMillis() - stat.beginTime;
        System.out.println(stat);
    }

    public static void error(Throwable error, Object obj) {
        HttpInfo stat = (HttpInfo) obj;
        stat.error=(error.getMessage());
        System.err.println(stat);
    }


    final static String voidSource = "{\n"
            + "%s"
            + "        try {\n"
            + "            %s$agent($$);\n"
            + "        } catch (Throwable e) {\n"
            + "%s"
            + "            throw e;\n"
            + "        }finally{\n"
            + "%s"
            + "        }\n"
            + "}\n";

    public static class HttpInfo{
        public long beginTime;
        public String url;
        public String clientIp;
        public String error;
        public long useTime;
        @Override
        public String toString() {
            return "HttpInfo{" +
                    "beginTime=" + new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss").format(new Date(beginTime)) +
                    ", url='" + url + '\'' +
                    ", clientIp='" + clientIp + '\'' +
                    ", error='" + error + '\'' +
                    ", useTime=" + useTime +
                    '}';
        }
    }

    private static class HttpServletRequestAdapter {
        private final Object target;
        private final Method _getRequestURI;
        private final Method _getRequestURL;
        private final Method _getParameterMap;
        private final Method _getMethod;
        private final Method _getHeader;
        private final Method _getRemoteAddr;
        private final static String targetClassName = "javax.servlet.http.HttpServletRequest";

        public HttpServletRequestAdapter(Object target) {
            this.target = target;
            try {
                Class<?> targetClass = target.getClass().getClassLoader().loadClass(targetClassName);
                _getRequestURI = targetClass.getMethod("getRequestURI");
                _getParameterMap = targetClass.getMethod("getParameterMap");
                _getMethod = targetClass.getMethod("getMethod");
                _getHeader = targetClass.getMethod("getHeader", String.class);
                _getRemoteAddr = targetClass.getMethod("getRemoteAddr");
                _getRequestURL = targetClass.getMethod("getRequestURL");
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("error :" + e.getMessage() + ". probable cause the target is not belong javax.servlet.http.HttpServletRequest ");
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("error :" + e.getMessage() + ". probable cause the target is not belong javax.servlet.http.HttpServletRequest ");
            }
        }


        public String getRequestURI() {
            try {
                return (String) _getRequestURI.invoke(target);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public String getRequestURL() {
            try {
                return _getRequestURL.invoke(target).toString();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public Map<String, String[]> getParameterMap() {
            try {
                return (Map<String, String[]>) _getParameterMap.invoke(target);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public String getMethod() {
            try {
                return (String) _getMethod.invoke(target);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public String getHeader(String name) {
            try {
                return (String) _getHeader.invoke(target, name);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public String getRemoteAddr() {
            try {
                return (String) _getRemoteAddr.invoke(target);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public String getClientIp() {
            String ip = getHeader("x-forwarded-for");
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = getRemoteAddr();
            }
            return ip;
        }
    }
}
    /*    newMethod.setBody(String.format(voidSource,beginSrc,Target_method,errorSrc,endSrc));
        ctClass.addMethod(newMethod);
        return ctClass.toBytecode();
    }

    private static  class HttpServletRequestAdapter{
        private final Object target;
        private final Method _getRequestURI;
        private final Method _getRequestURL;
        private final Method _getParameterMap;
        private final Method _getMethod;
        private final Method _getHeader;
        private final Method _getRemoteAddr;
        private static final String Target_classname = "javax.servlet.http.HttpServletRequest" ;
        public HttpServletRequestAdapter(Object target) throws ClassNotFoundException, NoSuchMethodException {
            this.target = target;
            Class<?> targetClass = target.getClass().getClassLoader().loadClass(Target_classname);
            _getRequestURI = targetClass.getMethod("getRequestURI");
            _getRequestURL = targetClass.getMethod("getRequestURL");
            _getParameterMap = targetClass.getMethod("getParameterMap");
            _getMethod = targetClass.getMethod("getMethod");
            _getHeader = targetClass.getMethod("getHeader",String.class);
            _getRemoteAddr = targetClass.getMethod("getRemoteAddr");
        }
        public String getRequestURI() throws InvocationTargetException, IllegalAccessException {
            return (String) _getRequestURI.invoke(target);
        }
        public String getRequestURL() throws InvocationTargetException, IllegalAccessException {
            return (String)_getRequestURL.invoke(target);
        }
        public Map<String,String[]> getParameterMap() throws InvocationTargetException, IllegalAccessException {
            return (Map<String,String[]>)_getParameterMap.invoke(target);
        }
        public String getMethod() throws InvocationTargetException, IllegalAccessException {
            return (String)_getMethod.invoke(target);
        }
        public String getHeader() throws InvocationTargetException, IllegalAccessException {
            return (String)_getHeader.invoke(target);
        }
        public String getRemoteAddr() throws InvocationTargetException, IllegalAccessException {
            return (String)_getRemoteAddr.invoke(target);
        }
    }



    public static HttpInfo begin(Object[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
        HttpInfo info = new HttpInfo();
        info.beginTime = System.currentTimeMillis();
        HttpServletRequestAdapter adapter = new HttpServletRequestAdapter(args[0]);
        info.url = adapter.getRequestURI();
        info.clientIp = adapter.getRequestURI();
        return info;
    }

    public static void end(Object obj) {
        HttpInfo stat = (HttpInfo)obj ;
        stat.useTime = System.currentTimeMillis()-stat.beginTime;
        System.out.println(stat);
    }
    public static void error(Throwable error , Object obj) {
        HttpInfo stat = (HttpInfo)obj;
        stat.error = error.getMessage();
        System.out.println(stat);
    }

    final static  String voidSource = "{\n" +
            "%s\n" +
            "   try{\n" +
            "       %s$agent($$);\n" +
            "   } cathc(Throwable e) {\n" +
            "     %s\n" +
            "       throw e ;\n" +
            "   }finally{\n" +
            "     %s" +
            "   }\n" +
            "}\n";

    public static class HttpInfo{
        public long beginTime ;
        public String url;
        public String clientIp;
        public String error;
        public long useTime;

        @Override
        public String toString() {
            return "HttpInfo{\n" +
                    " beginTime="+beginTime +
                    "\n url="+url +
                    "\n clientIp="+clientIp +
                    "\n error="+error +
                    "\n usrTime="+useTime +
                    "\n}";
        }
    }
}*/
